﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Tools.MAME
{
    /// <summary> Class used to filter database results. </summary>
    public class DatabaseFilter
    {
        #region Helper Enums and Classes

        private enum DataType
        {
            String,
            Numeric
        }

        private class GenericFilterItem
        {
            public String Field { get; private set; }
            public DataType FieldType { get; private set; }
            public Condition Condition { get; private set; }
            public String Value { get; private set; }
            public bool IncludeNulls { get; private set; }

            public GenericFilterItem(String field, DataType fieldType, Condition condition, String value,
                bool includeNulls)
            {
                Field = field;
                FieldType = fieldType;
                Condition = condition;
                Value = value;
                IncludeNulls = includeNulls;
            }

            public String SQL
            {
                get
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendFormat("([{0}]", Field);

                    switch (Condition)
                    {
                        case Condition.Equal:
                            sb.Append(" = ");
                            break;
                        case Condition.NotEqual:
                            sb.Append(" != ");
                            break;
                        case Condition.LessThan:
                            sb.Append(" < ");
                            break;
                        case Condition.LessThanOrEqual:
                            sb.Append(" <= ");
                            break;
                        case Condition.GreaterThan:
                            sb.Append(" > ");
                            break;
                        case Condition.GreaterThanOrEqual:
                            sb.Append(" >= ");
                            break;
                        default:
                            throw new NotImplementedException();
                    }

                    switch (FieldType)
                    {
                        case DataType.String:
                            sb.AppendFormat("'{0}'", Value);
                            break;
                        case DataType.Numeric:
                            sb.Append(Value);
                            break;
                        default:
                            throw new NotImplementedException();
                    }

                    if (IncludeNulls)
                        sb.AppendFormat(" and [{0}] is null)", Field);
                    else
                        sb.Append(")");

                    return sb.ToString();
                }
            }

            public override string ToString() { return SQL; }
        } // End FilterItem.

        #endregion

        #region Member Variables

        private List<GenericFilterItem> m_filterItems = new List<GenericFilterItem>();
        private List<String> m_categoryExcludeList = new List<string>();
        private List<String> m_controlExcludeList = new List<string>();
        private List<String> m_regionList = new List<string>();

        #endregion

        #region Properties

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets or sets a value indicating whether to exclude non-playable games. </summary>
        /// <value> True to exclude non playable games, false if not. </value>
        ///-------------------------------------------------------------------------------------------------
        public bool ExcludeNonPlayable { get; set; }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets or sets a value indicating whether to exclude games marked as mature. </summary>
        /// <value> True to exclude mature games, false if not. </value>
        ///-------------------------------------------------------------------------------------------------
        public bool ExcludeMature { get; set; }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets or sets a value indicating whether to exclude bootlegs. </summary>
        /// <value> True to exclude bootlegs, false if not. </value>
        ///-------------------------------------------------------------------------------------------------
        public bool ExcludeBootlegs { get; set; }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets or sets a value indicating whether to exclude hacks. </summary>
        /// <value> True to exclude hacks, false if not. </value>
        ///-------------------------------------------------------------------------------------------------
        public bool ExcludeHacks { get; set; }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets or sets a value indicating whether to exclude CHDs. </summary>
        /// <value> True to exclude CHDs, false if not. </value>
        ///-------------------------------------------------------------------------------------------------
        public bool ExcludeCHDs { get; set; }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets or sets the intelligent filter mode. </summary>
        /// <value> The intelligent filter mode. </value>
        ///-------------------------------------------------------------------------------------------------
        public IntelligentFilterMode FilterMode { get; set; }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets the regions. </summary>
        /// <value> The regions. </value>
        ///-------------------------------------------------------------------------------------------------
        public ICollection<String> Regions { get { return m_regionList; } }

        #endregion

        #region Constructor(s)

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Default constructor. </summary>
        ///-------------------------------------------------------------------------------------------------
        public DatabaseFilter()
        {
            ExcludeNonPlayable = false;
            ExcludeMature = false;
            ExcludeBootlegs = false;
            ExcludeHacks = false;
            ExcludeCHDs = false;
            FilterMode = IntelligentFilterMode.Off;
        }

        #endregion

        #region Public

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds a name filter. </summary>
        /// <param name="gameName">  Name of the game. </param>
        /// <param name="condition"> The condition. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AddNameFilter(String gameName, Condition condition)
        {
            m_filterItems.Add(new GenericFilterItem(SqlConst.GAME_NAME_COL, DataType.String, condition, gameName, false));
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds a year filter. </summary>
        /// <param name="year">         The year. </param>
        /// <param name="condition">    The condition. </param>
        /// <param name="matchUnknown"> True to include games whose year is unknown. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AddYearFilter(int year, Condition condition, bool matchUnknown)
        {
            m_filterItems.Add(new GenericFilterItem(SqlConst.YEAR_COL, DataType.Numeric, condition, year.ToString(), matchUnknown));
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds a smart player count filter. </summary>
        /// <param name="minPlayerCount"> Minimum number of players supported by target game list. </param>
        /// <param name="maxPlayerCount"> Maximum number of players supported by target game list. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AddSmartPlayerCountFilter(int? minPlayerCount, int? maxPlayerCount)
        {
            AddPlayerCountFilter(minPlayerCount.GetValueOrDefault(1), Condition.GreaterThanOrEqual, false);

            if (maxPlayerCount.HasValue)
                AddPlayerCountFilter(maxPlayerCount.Value, Condition.LessThanOrEqual, false);            
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds a player count filter. </summary>
        /// <param name="playerCount">  Number of players. </param>
        /// <param name="condition">    The condition. </param>
        /// <param name="matchUnknown"> True to include games whose player count is unknown. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AddPlayerCountFilter(int playerCount, Condition condition, bool matchUnknown)
        {
            m_filterItems.Add(new GenericFilterItem(SqlConst.INPUT_PLAYERS_COL, DataType.Numeric, condition, playerCount.ToString(), matchUnknown));
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds a smart button count filter. </summary>
        /// <param name="maxButtonCount"> Maximum number of butons supported by target game list. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AddSmartButtonCountFilter(int maxButtonCount)
        {
            AddButtonCountFilter(maxButtonCount, Condition.LessThanOrEqual, false);
            AddButtonCountFilter(0, Condition.GreaterThanOrEqual, false);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds a button count filter. </summary>
        /// <param name="buttonCount">  Number of buttons. </param>
        /// <param name="condition">    The condition. </param>
        /// <param name="matchUnknown"> True to include games whose button count is unknown. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AddButtonCountFilter(int buttonCount, Condition condition, bool matchUnknown)
        {
            m_filterItems.Add(new GenericFilterItem(SqlConst.INPUT_BUTTONS_COL, DataType.Numeric, condition, buttonCount.ToString(), matchUnknown));
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds category to be excluded. </summary>
        /// <exception cref="ArgumentException"> Thrown when one or more arguments have unsupported or
        /// illegal values. </exception>
        /// <param name="category"> The category to exclude. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AddCategoryExclude(String category)
        {
            if (String.IsNullOrWhiteSpace(category))
                throw new ArgumentException("Category must be defined.", "category");

            if (!m_categoryExcludeList.Contains(category, StringComparer.OrdinalIgnoreCase))
                m_categoryExcludeList.Add(category);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds a control to be excluded. i.e. Any game requiring specified control will be
        /// excluded. </summary>
        /// <param name="control"> The control to exclude. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AddControlExclude(String control) { m_controlExcludeList.Add(control); }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Adds a region ot the list. How this region is used depends on the setting of the
        /// region filter mode. </summary>
        /// <param name="region"> The region to add. </param>
        ///-------------------------------------------------------------------------------------------------
        public void AddRegion(String region) { m_regionList.Add(region); }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets SQL matching basic filter options, nont including any 'one-per' filters which
        /// require external processing. </summary>
        /// <value> The SQL. </value>
        ///-------------------------------------------------------------------------------------------------
        public String BasicSQL
        {
            get
            {
                StringBuilder sb = new StringBuilder();

                if (ExcludeNonPlayable)
                {
                    // Check bios, device, runnable, and driver status.
                    sb.AppendFormat("([{0}] = 0 and [{1}] = 0 and [{2}] = 1 and [{3}] in ({4}, {5}))",
                        SqlConst.IS_BIOS_COL, SqlConst.IS_DEVICE_COL, SqlConst.RUNNABLE_COL, SqlConst.DRIVER_STATUS_COL,
                        Convert.ToInt32(DriverAttributeStatus.good), Convert.ToInt32(DriverAttributeStatus.imperfect));
                }

                if (ExcludeMature)
                {
                    if (sb.ToString().Length > 0)
                        sb.Append(" and ");

                    // Allow nulls in case mature is unknown. i.e. We're only filtering out 'known' mature titles.
                    sb.AppendFormat("([{0}] = 0 or [{0}] is null)", SqlConst.IS_MATURE_COL);
                }

                if (ExcludeBootlegs)
                {
                    if (sb.ToString().Length > 0)
                        sb.Append(" and ");

                    sb.AppendFormat("[{0}] not like '%(%bootleg%)%'", SqlConst.DESCRIPTION_COL);
                }

                if (ExcludeHacks)
                {
                    if (sb.ToString().Length > 0)
                        sb.Append(" and ");

                    sb.AppendFormat("[{0}] not like '%(%hack%)%'", SqlConst.DESCRIPTION_COL);
                }

                if (ExcludeCHDs)
                {
                    if (sb.ToString().Length > 0)
                        sb.Append(" and ");

                    sb.AppendFormat("[{0}] = 0", SqlConst.CHD_REQUIRED);
                }

                foreach (GenericFilterItem item in m_filterItems)
                {
                    if (sb.ToString().Length > 0)
                        sb.Append(" and ");

                    sb.AppendFormat(item.SQL);
                }

                if (m_categoryExcludeList.Count > 0)
                {
                    if (sb.ToString().Length > 0)
                        sb.Append(" and ");

                    if (m_categoryExcludeList.Count == 1)
                    {
                        sb.AppendFormat("[{0}] != '{1}'", SqlConst.CATEGORY_COL, m_categoryExcludeList[0]);
                    }
                    else
                    {
                        sb.AppendFormat("[{0}] not in ('{1}'", SqlConst.CATEGORY_COL, m_categoryExcludeList[0]);
                        for (int i = 1; i < m_categoryExcludeList.Count; i++)
                            sb.AppendFormat(", '{0}'", m_categoryExcludeList[i]);

                        sb.Append(")");
                    }
                }

                if (m_controlExcludeList.Count > 0)
                {
                    if (sb.ToString().Length > 0)
                        sb.Append(" and ");

                    sb.AppendFormat("[{0}] not in (select [{0}] from [{1}] where ",
                        SqlConst.GAME_NAME_COL, SqlConst.CONTROL_TABLE);

                    if (m_controlExcludeList.Count == 1)
                    {
                        sb.AppendFormat("[{0}] = '{1}'", SqlConst.TYPE_COL, m_controlExcludeList[0]);
                    }
                    else
                    {
                        sb.AppendFormat("[{0}] in ('{1}'", SqlConst.TYPE_COL, m_controlExcludeList[0]);
                        for (int i = 1; i < m_controlExcludeList.Count; i++)
                            sb.AppendFormat(", '{0}'", m_controlExcludeList[i]);

                        sb.Append(")");
                    }

                    sb.Append(")");
                }

                if ((FilterMode == IntelligentFilterMode.Region) && (m_regionList.Count > 0))
                {
                    if (sb.ToString().Length > 0)
                        sb.Append(" and ");

                    if (m_regionList.Count == 1)
                    {
                        sb.AppendFormat("[{0}] = '{1}'", SqlConst.REGION_COL, m_regionList[0]);
                    }
                    else
                    {
                        sb.AppendFormat("[{0}] in ('{1}'", SqlConst.REGION_COL, m_regionList[0]);
                        for (int i = 1; i < m_regionList.Count; i++)
                            sb.AppendFormat(", '{0}'", m_regionList[i]);

                        sb.Append(")");
                    }
                }

                return sb.ToString();
            }
        }

        #endregion
    }
}
